﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.Text;
using Cips.DataMining.BingTranslator;
using Cips.DataMining.IO;

namespace Cips.DataMining.Language
{
	public class TranslationDictionary
	{
		readonly ITranslationService service;

		public TranslationDictionary(ITranslationService service)
		{
			this.service = service;
		}

		public string CachePath { get; set; }

		public IEnumerable<string> GetTranslations(IEnumerable<string> source)
		{
			if (source == null) 
				throw new ArgumentNullException("source");

			var input = source.ToList();

			if (CachePath == null)
				return service.GetTranslations(input);

			Dictionary<string, string> cache;
			try
			{
				cache = CsvDictionary.Read(File.OpenRead(CachePath));
			}
			catch(FileNotFoundException)
			{
				cache = new Dictionary<string, string>();
			}

			var missing = input.Where(w => !cache.ContainsKey(w)).Distinct().ToList();

			try
			{
				var newItems = service.GetTranslations(missing);

				for (var i = 0; i < missing.Count; i++)
					cache.Add(missing[i], newItems[i]);

				CsvDictionary.Write(File.OpenWrite(CachePath), cache);

				return input.Select(w => cache[w]);
			}
			catch (PartialTranslationException e)
			{
				for (var i = 0; i < e.Translations.Count; i++)
					cache.Add(missing[i], e.Translations[i]);

				CsvDictionary.Write(File.OpenWrite(CachePath), cache);

				throw;
			}
		}
	}

	public interface ITranslationService
	{
		string[] GetTranslations(List<string> texts);
	}

	public class BingTranslationService : ITranslationService
	{
		readonly string appId;
		readonly string sourceLanguage;
		readonly string targetLanguage;

		public BingTranslationService(string appId, string sourceLanguage, string targetLanguage)
		{
			this.appId = appId;
			this.sourceLanguage = sourceLanguage;
			this.targetLanguage = targetLanguage;
		}

		public string[] GetTranslations(List<string> texts)
		{
			var translator = new LanguageServiceClient(new BasicHttpBinding(), new EndpointAddress("http://api.microsofttranslator.com/V2/soap.svc"));
			var options = new TranslateOptions();
			var retval = new List<string>();

			try
			{
				for (var w = 0; w < (texts.Count + 99) / 100; w++)
				{
					var response = translator.GetTranslationsArray(appId, texts.Skip(w * 100).Take(100).ToArray(), sourceLanguage, targetLanguage, 100,
																   options);

					retval.AddRange(response.Select(t => t.Translations.First().TranslatedText).ToArray());
				}
			}
			catch(Exception e)
			{
				if (retval.Count > 0)
					throw new PartialTranslationException(retval, e);

				throw;
			}

			return retval.ToArray();
		}
	}

	public class PartialTranslationException : Exception
	{
		readonly List<string> translations;

		public PartialTranslationException(List<string> translations, Exception innerException)
			: base("Translation operation failed with partial results", innerException)
		{
			this.translations = translations;
		}

		public List<string> Translations
		{
			get { return translations; }
		}
	}
}
